
import base64
import hashlib
import select
from threading import *
import math
import sys
import random
import time
import richiesta
import httpbase
import rete
chiaveWebSocket = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"

def checkSocket(headers):
    richiesti = ['Sec-WebSocket-Version','Upgrade','Host','Connection','Sec-WebSocket-Key']
    if all(headers.get(r) != "" for r in richiesti):
        connection = headers.get("Connection").lower().split(", ")
        upgrade = headers.get("Upgrade").lower().strip()
        if "upgrade" in connection and upgrade == "websocket" \
            and int(headers.get("Sec-WebSocket-Version")) == 13:
            if len(base64.b64decode(headers.get("Sec-WebSocket-Key"))) == 16:
                return True
    return False

def gestisciHandshakeWebSocket(req):
    global pacchetto
    secWSKey = req.headers.get("Sec-WebSocket-Key")
    servKey = (secWSKey + chiaveWebSocket).encode("ASCII")
    sha1 = hashlib.sha1(servKey).digest()
    secAccept = base64.b64encode(sha1).decode("UTF-8")
    headerNuovi = [
        ('Sec-WebSocket-Accept', secAccept),
        ('Upgrade', 'websocket'),
        ('Connection', 'upgrade')
    ]
    richiesta.mandaHeaders(req, 101, headerNuovi)
    req.aperta = True
    req.sentCloseWS = False
    #rete.threadBloccante(req, parseMessaggioWS)
def sendCloseWS(req,intReason=0,textReason=""):
    rawPayload = bytearray()
    if intReason != 0:
        length = 2
        rawPayload = struttura([16],[intReason])+bytearray(textReason,encoding="UTF-8")
    MessaggioDaInviareWS(req, rawPayload, raw=True, opcode=8)
    req.sentCloseWS= True

def closeWS(req,motivo):
    req.aperta = False
    req.log("socket chiuso perché: "+motivo)
    rete.chiudi(req.connessione)
    #sys.exit()

#class RichiestaWS(httpbase.RichiestaGET):
    #def __init__(self, intestazione, headers, connessione, keepAlive = True):
        #super().__init__(intestazione, headers,connessione,parseQuery=True, keepAlive = keepAlive)
        #self.wsocket = True
        #self.aperta = True
        #self.sentCloseWS = False
        #self.gestoreRichiesta = gestisciHandshakeWebSocket

def parseMessaggioWS(data,req, datiPrecedenti,qu):
    #lettura header
    haveHeader = False
    lunghPayload = lunghPayloadIniz= 0
    payload = bytearray()
    avanzo = bytes()
    fineHeader = maskByte = byteRicevuti = 0
    if datiPrecedenti is not None:
        oldData,haveHeader,Fin,opcode,maskbit,lunghPayload,payload,byteRicevuti, \
            maschera,maskByte,continuazione = datiPrecedenti
        if oldData:
            data= oldData+data
        if continuazione:
            lunghPayloadIniz= lunghPayload
    #valori di ritorno più usati
    nonAbbastanza = (parseMessaggioWS, (data,False,False,False,False,False,bytearray(),False,False,False,False),bytes())
    fineMessaggio = (parseMessaggioWS, None, bytes())
    #print("--RECV"+str(data))
    if not haveHeader:
        if len(data) < 2:
            return nonAbbastanza
        Fin, rsv, nuovoOpcode, maskbit, len1 = destruttura([1,3,4,1,7],data[0:2])
        if nuovoOpcode != 0:
            opcode= nuovoOpcode
        
        if rsv != 0:
            closeWS(req, "rsv = "+str(rsv))
        fineHeader = 2
        if len1 <= 125:
            #print("caso 1 ")
            #è la lunghezza vera
            lunghPayload += len1
        elif len1 == 126:
            if len(data) < 4:
                return nonAbbastanza
            #print("caso 2 ")
            #con len1 = 126, la vera lunghezza è len2
            len2 , = destruttura([16],data[2:4])
            fineHeader += 2
            lunghPayload += len2
        elif len1 == 127:
            if len(data) < 10:
                return nonAbbastanza
            #print("caso 3 ")
            fineHeader += 8
            #con len1 = 127, la vera lunghezza sono i 64 bit dopo mask
            len3, = destruttura([64],data[2:10])    #la virgola ci vuole
            lunghPayload += len3
        
        if maskbit:
            if len(data) < fineHeader+4:
                return nonAbbastanza
            maschera= destruttura([8,8,8,8], data[fineHeader:fineHeader+4])
            fineHeader += 4
        haveHeader = True
        req.log("messaggio con opcode: %d" %opcode)
    #lettura payload
    if maskbit:
        
        for byte in data[fineHeader:fineHeader+lunghPayload-lunghPayloadIniz]:
            payload.append(byte ^ maschera[maskByte])
            maskByte += 1
            byteRicevuti += 1
            if maskByte == 4:
                maskByte = 0
                
        if len(data) > lunghPayload-lunghPayloadIniz:
           avanzo = data[fineHeader+lunghPayload-lunghPayloadIniz:]
    else: 
        closeWS(req,"Unmasked frame")
        return (False,None,bytes())
        #payload = data[fineHeader:fineHeader+lunghPayload]
    if opcode == 8:
        if lunghPayload != 0:
            num, = destruttura([16],payload[0:2])
            resto = payload[2:].decode("UTF-8")
        else:
            num = 0
            resto = ""
        if not req.sentCloseWS:
            sendCloseWS(req,num, resto)
            closeWS(req, "richiesta chiusura client, int: "+str(num)+", txt: "+resto)
        else:
            closeWS(req, "il client ha ricevuto la richiesta di chiusura del server, int: "+
                str(num)+", txt: "+resto)   
    if byteRicevuti >= lunghPayload:    #se il frame è finito
        if not Fin:
            #frame di continuazione: scarsamente testato
            #perché il browser non lo fa. Esempio messaggio di test: 
            #messaggio = b'\x01\x84\x00\x00\x00\x01panb\x82\x85\x00\x04\x00\x00epta '
            #da usare con aTradimento in rete.py
            return (parseMessaggioWS, (b"",False,False,opcode,False,lunghPayload,payload,byteRicevuti,False,False,True),avanzo)
        else:
            if opcode == 9:   #ping
                #send pong
                m = MessaggioDaInviareWS(req, payload,raw=True,l=lunghPayload,opcode=10)
                return fineMessaggio
            elif opcode == 10:  #pong:
                req.log("pong: %s" % payload.decode("UTF-8"))
                return fineMessaggio
            msg = MessaggioRicevutoWS(req, payload, byteRicevuti)
            msg.gestisci()
            return fineMessaggio
    else:
        return (parseMessaggioWS, (b"",haveHeader,Fin,opcode,maskbit,lunghPayload,payload,byteRicevuti,maschera,maskByte,False),avanzo)

class MessaggioRicevutoWS:
    def __init__(self,req,payload,byteRicevuti):
        self.connessione = req.connessione
        self.payload = payload
        self.richiesta= req
        self.byteRicevuti = byteRicevuti
    def gestisci(self):
        m = MessaggioDaInviareWS(self.richiesta,"Arriva l'eco!")
        #time.sleep(3)
        m = MessaggioDaInviareWS(self.richiesta, "pingami!",opcode=9)
        m = MessaggioDaInviareWS(self.richiesta,self.payload,raw=True, final=False)
        m = MessaggioDaInviareWS(self.richiesta,"a tutti", continuation= True,final=True)
        #sendCloseWS(self.richiesta, 3,"te saludi")

class MessaggioDaInviareWS:
    def __init__(self,req,payload,raw=False,l = -1, opcode = 1, continuation = False, final=True):
        #maschera = [random.randint(0,pow(2,8)-1) for x in range(4)]
        if not req.aperta:
            return
        if not raw:
            cifrato = bytearray(payload,encoding="UTF-8")
        else: cifrato = payload
        #if l==-1:
        l= len(cifrato)
        #num = 0
        #indice = 0
        #for byte in cifrato:
        #    cifrato[indice] = byte ^ maschera[num]
        #    num += 1
        #    indice+=1
        #    if num == 4:
        #        num = 0
        #print(cifrato)
        fin = 1 if final else 0
        if continuation:
            opcode = 0
        if l <= 125:
            #caso normale
            struct = struttura([1,3,4,1,7],[fin,0,opcode,0,l])
        elif 125 < l < (pow(2,16)-1):
            #caso b
            struct = struttura([1,3,4,1,7,16],[fin,0,opcode,0,126,l])
        elif l < pow(2,64):
            struct = struttura([1,3,4,1,7,64],[fin,0,opcode,0,127,l])
            #print(bin(int.from_bytes(struct,byteorder="big")))
        else:
            raise Exception("Alla faccia del messaggio")
        
        rete.scrivi(req.connessione, struct)
        rete.scrivi(req.connessione, cifrato)
#utilità binarie
def acceso(data, nBit, l = -1):
    return fettaInt(data, nBit, nBit+1) == 1

def spento(data, nBit):
    return not acceso(data,nBit)

def fettaInt(data, start, end):
    confineSinistra = start//8
    confineDestra = math.ceil((end)/8) 
    fetta = data[confineSinistra:confineDestra]
    blob = int.from_bytes(fetta, byteorder="big") >> (confineDestra*8 - end)
    num = i = 0
    while end > start:
        num += pow(2,i)* (blob & 1)
        blob = blob >> 1
        end -= 1
        i += 1
    return num

def destruttura(listaPattern,dati):
    siamoA = 0
    ret = ()
    for x in listaPattern:
        val = fettaInt(dati, siamoA, siamoA+x)
        if x == 1:
            val = val == 1
        ret += val, #con la virgola lo tratta come tupla con 1 solo elemento
        siamoA += x
    return ret

def aggiungiIntInCoda(intero, spostamento, intero2):
    return (intero << spostamento )| intero2
def toByteArray(intero):
    arr = bytearray()
    while intero != 0:
        curr = intero & 0xff
        arr.insert(0,curr)
        intero = intero >> 8
    return arr
def struttura(listaPattern,dati):
    intero = 255
    indice = 0
    arr = bytes()
    siamoA = 0
    for x in listaPattern:
        val = dati[indice]
        if x == 1:
            val = bool(val)
        intero = aggiungiIntInCoda(intero, x, val)
        indice += 1
    return toByteArray(intero)[1:]
